package middleware

import (
	"context"
	"github.com/opentracing/opentracing-go"
	"github.com/opentracing/opentracing-go/ext"
	"go-programming-tour-book/tag-service/global"
	"go-programming-tour-book/tag-service/pkg/metatext"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"time"
)

/*
超时控制（上下文）
*/
func defaultContextTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
	var cancel context.CancelFunc
	if _, ok := ctx.Deadline(); !ok {
		defaultTimeout := 60 * time.Second
		//设置默认超时时间为 60s（该时间是针对整条调用链路的）
		ctx, cancel = context.WithTimeout(ctx, defaultTimeout)
	}

	return ctx, cancel
}

func UnaryContextTimeout() grpc.UnaryClientInterceptor {
	return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		ctx, cancel := defaultContextTimeout(ctx)
		if cancel != nil {
			defer cancel()
		}

		return invoker(ctx, method, req, reply, cc, opts...)
	}
}

func StreamContextTimeout() grpc.StreamClientInterceptor {
	return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
		ctx, cancel := defaultContextTimeout(ctx)
		if cancel != nil {
			defer cancel()
		}

		return streamer(ctx, desc, cc, method, opts...)
	}
}

func ClientTracing() grpc.UnaryClientInterceptor {
	return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
		var parentCtx opentracing.SpanContext             //用于在分布式系统中的不同服务之间传递跟踪信息
		var spanOpts []opentracing.StartSpanOption        //用于在开始一个新跨度（Span）时提供额外的配置选项
		var parentSpan = opentracing.SpanFromContext(ctx) //用于从给定的上下文中提取 OpenTracing 的 Span（跨度）
		if parentSpan != nil {
			parentCtx = parentSpan.Context()                            //用于获取当前跟踪 Span 的上下文信息，返回一个opentracing.SpanContext对象
			spanOpts = append(spanOpts, opentracing.ChildOf(parentCtx)) //opentracing.ChildOf 关系表示在分布式追踪系统中，一个 span 是另一个 span 的直接子操作。
		}
		spanOpts = append(spanOpts, []opentracing.StartSpanOption{
			opentracing.Tag{Key: string(ext.Component), Value: "gRPC"},
			ext.SpanKindRPCClient, //其实就是 opentracing.Tag{Key: string(SpanKind), Value: "client"}
		}...)

		span := global.Tracer.StartSpan(method, spanOpts...) //开始一个新的跨度，并返回一个表示该跨度的对象
		defer span.Finish()

		md, ok := metadata.FromOutgoingContext(ctx) //从 服务端 发送的上下文中提取出 metadata
		if !ok {
			md = metadata.New(nil)
		}
		_ = global.Tracer.Inject(span.Context(), opentracing.TextMap, metatext.MetadataTextMap{MD: md}) //将一个跨度的上下文编码为指定格式，并将其注入到载体（第三个参数）中
		newCtx := opentracing.ContextWithSpan(metadata.NewOutgoingContext(ctx, md), span)               //将给定跨度与现有的上下文进行关联
		return invoker(newCtx, method, req, reply, cc, opts...)
	}
}
